package eval;

import network.Network;
import network.Rule;
import networkLoader.DeltaNetwork;
import org.jgrapht.alg.util.Pair;
import verifier.APKeepVerifier;
import verifier.AtomVerifier;
import verifier.JiffyVerifier;
import verifier.Ports.ArrayPorts;
import verifier.Ports.PersistentPorts;
import verifier.Ports.Ports;
import verifier.util.Changes;

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.zip.GZIPInputStream;

public class DeltanetEval {
    private static Network network;
    private static double memoryBefore, bytesToMega = 1024L * 1024L;
    private static int warmup = 0, test = 1;
    private static double ratio;

    public static void main(String[] args) throws IOException {
        // network = DeltaNetwork.getNetwork("airtel1");
        // process(network);

        network = DeltaNetwork.getNetwork("berkley");
        process(network);

        network = DeltaNetwork.getNetwork("inet");
        process(network);

        network = DeltaNetwork.getNetwork("airtel2");
        process(network);
    }


    public static void process(Network network) throws IOException {
        System.gc();
        System.runFinalization();
        System.out.println("# updates: " + network.updateSequence.size() + " # Switches: " + network.getAllDevices().size());
        memoryBefore = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        System.out.println("Memory usage (storing network): " + (memoryBefore / bytesToMega) + " M");

        ratio = 1000L * network.updateSequence.size() * test;
        overall(network);
    }


    private static double s1, s2, s3, s4, s5;
    private static double t1, t2, t3, t4, t5;

    private static void overall(Network network) throws IOException {
        System.out.println("+++++++++++++++++++++ " + network.getName() + " +++++++++++++++++++++");
        s1 = s2 = s3 = s4 = s5 = 0;
        t1 = t2 = t3 = t4 = t5 = 0;

        for (int i = 0; i < warmup; i ++) seq(network, true);
        System.out.println("==================== Loaded ==================== ");
        for (int i = 0; i < test; i ++) s3 += seq(network, true);
        System.out.println("==================== Ended ==================== ");
        for (int i = 0; i < warmup; i ++) deltanet(network);
        System.out.println("==================== Loaded ==================== ");
        for (int i = 0; i < test; i ++) s1 += deltanet(network);
        System.out.println("==================== Ended ==================== ");
        for (int i = 0; i < warmup; i ++) batch(network, 100);
        System.out.println("==================== Loaded ==================== ");
        for (int i = 0; i < test; i ++) s4 += batch(network, 100);
        /*
        System.out.println("==================== Ended ==================== ");
        for (int i = 0; i < warmup; i ++) seq(network, false);
        System.out.println("==================== Loaded ==================== ");
        for (int i = 0; i < test; i ++) s5 += seq(network, false);
        System.out.println("==================== Ended ==================== ");
        for (int i = 0; i < warmup; i ++) apkeep(network, new ArrayPorts());
        System.out.println("==================== Loaded ==================== ");
        for (int i = 0; i < test; i ++) s2 += apkeep(network, new ArrayPorts());
        System.out.println("==================== Ended ==================== ");
         */

        FileWriter fileWriter = new FileWriter("overall.txt", true);
        PrintWriter printWriter = new PrintWriter(fileWriter);
        printWriter.println();
        printWriter.println();
        printWriter.println();
        printWriter.println();
        printWriter.println(network.getName() + " Amoritized time: (deltanet, apkeep, jiffy, jiffy (100 batch), jiffy w/o imt) = (" +
                (s1 / ratio) + ", " + (s2 / ratio) + ", " + (s3 / ratio)  + ", " + (s4 / ratio) + ", " + (s5 /ratio) + " ) us.");
        printWriter.println(network.getName() + " Total time: (deltanet, apkeep, jiffy, jiffy (100 batch), jiffy w/o imt) = (" +
                (s1 / test) + ", " + (s2 / test) + ", " + (s3 / test) + ", " + (s4 / test) + ", " + (s5 / test) + " ) ns.");
        printWriter.println(network.getName() + " Operations: (deltanet, apkeep, jiffy, jiffy (100 batch), jiffy w/o imt) = (" +
                (t1 / (warmup + test)) + ", " + (t2 / (warmup + test)) + ", " + (t3 / (warmup + test)) + ", " + (t5 / (warmup + test)) + " ).");
        printWriter.close();

        System.out.println("+++++++++++++++++++++ END " + network.getName() + " END +++++++++++++++++++++");
    }

    private static void printMemory() {
        System.gc();
        double memoryAfter = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        System.out.println("Memory usage (verification): " + ((memoryAfter - memoryBefore) / bytesToMega) + " M");
    }

    private static double deltanet(Network network) {
        System.gc();

        AtomVerifier verifier = new AtomVerifier();
        for (Pair<Boolean, Rule> pair : network.updateSequence) {
            Rule rule = pair.getSecond();
            if (pair.getFirst()) {
                verifier.insertRule(rule);
            } else {
                verifier.removeRule(rule);
            }
        }
        System.out.println("Delta-net #Atom: " + verifier.atomSize());
        printMemory();
        t1 += verifier.opCnt;
        return verifier.printTime(network.updateSequence.size());
    }

    private static double apkeep(Network network, Ports base) {
        System.gc();
        APKeepVerifier verifier = new APKeepVerifier(network, base);
        for (Pair<Boolean, Rule> pair : network.updateSequence) {
            Rule rule = pair.getSecond();
            if (pair.getFirst()) {
                verifier.insertRule(rule);
                verifier.update(true);
            } else {
                verifier.removeRule(rule);
                verifier.update(true);
            }
        }
        System.out.println("APKeep #EC: " + verifier.predSize());
        printMemory();
        t2 += verifier.bddEngine.opCnt;
        return verifier.printTime(network.updateSequence.size());
    }

    private static double seq(Network network, boolean useFFMT) {
        System.gc();
        JiffyVerifier verifier = new JiffyVerifier(network, new PersistentPorts());
        if (useFFMT) {
            ArrayList<Rule> insertion = new ArrayList<>(), deletion = new ArrayList<>();
            for (Pair<Boolean, Rule> pair : network.updateSequence) {
                if (pair.getFirst()) insertion.add(pair.getSecond()); else deletion.add(pair.getSecond());
            }
            Changes changes = verifier.miniBatch(insertion, deletion);
            verifier.update(changes);
            t3 += verifier.bddEngine.opCnt;
        } else {
            for (Pair<Boolean, Rule> pair : network.updateSequence) {
                Rule rule = pair.getSecond();
                if (pair.getFirst()) {
                    Changes changes = verifier.insertMiniBatch(new ArrayList<>(Collections.singletonList(rule)));
                    verifier.update(changes);
                } else {
                    Changes changes = verifier.miniBatch(new ArrayList<>(), new ArrayList<>(Collections.singletonList(rule)));
                    verifier.update(changes);
                }
            }
            t5 += verifier.bddEngine.opCnt;
        }
        System.out.println("Jiffy #EC: " + verifier.predSize() + (useFFMT ? " with FFMT" : " w/o FFMT"));
        printMemory();
        return verifier.printTime(network.updateSequence.size());
        // Checker.allPair(network, new PPM(verifier.portsToPredicate).getPortToPreds());
    }

    private static double batch(Network network, int size) {
        System.gc();
        JiffyVerifier verifier = new JiffyVerifier(network, new PersistentPorts());

        int cnt = 0;
        ArrayList<Rule> insertion = new ArrayList<>(), deletion = new ArrayList<>();
        for (Pair<Boolean, Rule> pair : network.updateSequence) {
            cnt ++;
            if (pair.getFirst()) insertion.add(pair.getSecond()); else deletion.add(pair.getSecond());

            if (cnt % size == 0) {
                Changes changes = verifier.miniBatch(insertion, deletion);
                verifier.update(changes);

                insertion.clear();
                deletion.clear();
            }
        }
        if (cnt % size != 0) {
            Changes changes = verifier.miniBatch(insertion, deletion);
            verifier.update(changes);
        }

        System.out.println("Jiffy #EC: " + verifier.predSize() + (" 100 batch"));
        printMemory();
        t4 += verifier.bddEngine.opCnt;
        return verifier.printTime(network.updateSequence.size());
    }
}
